import os
import signal
import sys
import threading

from flask import Flask, url_for, redirect, render_template, request
from flask_sqlalchemy import SQLAlchemy
from wtforms import form, fields, validators
import flask_admin as admin
import flask_login as login

from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib import sqla
from flask_admin import helpers, expose
from werkzeug.security import generate_password_hash, check_password_hash
from flask_babel import Babel
from loguru import logger
from sqlalchemy.sql import func

from conf import Config as myconf
from utils.JsonResponse import JsonResponse
from utils.comm1 import SingletonMeta

# Create Flask application
app = Flask(__name__)

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'

# Create in-memory database
app.config['DATABASE_FILE'] = 'maininfo.sqlite'  # 使用相对路径，则会在 src/instance 目录下创建数据库文件
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
babel = Babel(app)


################# Beg of auth-flask-login 相关 #################

# Create user model.
class User(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  sname = db.Column(db.String(100))
  login = db.Column(db.String(80), unique=True)
  email = db.Column(db.String(120))
  password = db.Column(db.String(180))

  # Flask-Login integration
  # NOTE: is_authenticated, is_active, and is_anonymous
  # are methods in Flask-Login < 0.3.0
  @property
  def is_authenticated(self):
    return True

  @property
  def is_active(self):
    return True

  @property
  def is_anonymous(self):
    return False

  def get_id(self):
    return self.id

  # Required for administrative interface
  def __unicode__(self):
    return self.username


# Define login and registration forms (for flask-login)
class LoginForm(form.Form):
  login = fields.StringField(validators=[validators.InputRequired()])
  password = fields.PasswordField(validators=[validators.InputRequired()])

  def validate_login(self, field):
    user = self.get_user()

    if user is None:
      raise validators.ValidationError('Invalid user')

    # we're comparing the plaintext pw with the the hash from the db
    if not check_password_hash(user.password, self.password.data):
      # to compare plain text passwords use
      # if user.password != self.password.data:
      raise validators.ValidationError('Invalid password')

  def get_user(self):
    logger.info(f"Getting user : {self.login.data}")
    return db.session.query(User).filter_by(login=self.login.data).first()


class RegistrationForm(form.Form):
  login = fields.StringField(validators=[validators.InputRequired()])
  email = fields.StringField()
  password = fields.PasswordField(validators=[validators.InputRequired()])

  def validate_login(self, field):
    if db.session.query(User).filter_by(login=self.login.data).count() > 0:
      raise validators.ValidationError('Duplicate username')


# Initialize flask-login
def init_login():
  login_manager = login.LoginManager()
  login_manager.init_app(app)

  # Create user loader function
  @login_manager.user_loader
  def load_user(user_id):
    return db.session.query(User).get(user_id)


# Create customized model view class
class MyModelView(sqla.ModelView):

  def is_accessible(self):
    return login.current_user.is_authenticated


# Create customized index view class that handles login & registration
class MyAdminIndexView(admin.AdminIndexView):

  @expose('/')
  def index(self):
    if not login.current_user.is_authenticated:
      return redirect(url_for('.login_view'))
    return super(MyAdminIndexView, self).index()

  @expose('/login/', methods=('GET', 'POST'))
  def login_view(self):
    # handle user login
    form = LoginForm(request.form)
    if helpers.validate_form_on_submit(form):
      user = form.get_user()
      login.login_user(user)

    if login.current_user.is_authenticated:
      return redirect(url_for('.index'))
    link = '<p>Don\'t have an account? <a href="' + url_for('.register_view') + '">Click here to register.</a></p>'
    self._template_args['form'] = form
    self._template_args['link'] = link
    return super(MyAdminIndexView, self).index()

  @expose('/register/', methods=('GET', 'POST'))
  def register_view(self):
    form = RegistrationForm(request.form)
    if helpers.validate_form_on_submit(form):
      user = User()

      form.populate_obj(user)
      # we hash the users password to avoid saving it as plaintext in the db,
      # remove to use plain text:
      user.password = generate_password_hash(form.password.data)

      db.session.add(user)
      db.session.commit()

      login.login_user(user)
      return redirect(url_for('.index'))
    link = '<p>Already have an account? <a href="' + url_for('.login_view') + '">Click here to log in.</a></p>'
    self._template_args['form'] = form
    self._template_args['link'] = link
    return super(MyAdminIndexView, self).index()

  @expose('/logout/')
  def logout_view(self):
    login.logout_user()
    return redirect(url_for('.index'))


# Flask views
@app.route('/')
def index():
  return render_template('index.html')


# Initialize flask-login
init_login()

# Create admin
admin = admin.Admin(app, 'Example: Auth', index_view=MyAdminIndexView(),
                    theme=Bootstrap4Theme(base_template='my_master.html'))

# Add view
admin.add_view(MyModelView(User, db.session))


def build_init_db():
  logger.info("Building initial database")
  db.drop_all()
  db.create_all()
  admin_user = User(login="admin", password=generate_password_hash("admin@pwd"))
  db.session.add(admin_user)
  db.session.commit()

  # 初始化定义在 业务Models中的表结构
  from models.table_defs import init_tables, PaperAnalyzeMain, TaskQueue
  init_tables(db.get_engine())  # 初始化数据库表结构, 仅需要时运行


################# End of auth-flask-login 相关 #################

################# Beg of 业务 相关 #################

from werkzeug.utils import secure_filename

ALLOWED_EXTENSIONS = {'pdf'}
app.config['UPLOAD_FOLDER'] = myconf.upload_dir


def allowed_file(filename):
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/upload_paper_for_analyze/<act>', methods=['POST'])
def upload_paper_for_analyze(act):
  if 'file' not in request.files:
    return redirect(request.url)
  file = request.files['file']
  if file.filename == '':
    return redirect(request.url)
  if file and allowed_file(file.filename):
    my_filename = secure_filename(file.filename)
    main_mgr, main_task_thread = MgrFactory().get_objs()
    if main_mgr:
      main_mgr.add_paper_analyze(file, my_filename, act)
      return JsonResponse.success(data={"filename": my_filename, "act": act}).to_dict()
    else:
      return JsonResponse.error(code=500, msg="main_mgr not found.").to_dict()
  else:
    return JsonResponse.error(code=500, msg="'文件类型不允许'.").to_dict()


################# End of 业务 相关 #################

################# Beg of 管理 相关 #################
from biz.main_mgr import main_mgr


class MgrFactory(metaclass=SingletonMeta):
  def __init__(self):
    self.mgr = main_mgr()
    self.main_task_thread = threading.Thread(target=self.mgr.start_task)

  def get_objs(self):
    return self.mgr, self.main_task_thread


def signal_handler(sig, frame):
  print('Received signal, stopping main_task_thread and exit...')
  main_mgr, main_task_thread = MgrFactory().get_objs()
  if main_mgr and main_task_thread:
    main_mgr.stop_task()
    main_task_thread.join()
  sys.exit(0)


def init_main_mgr_task():
  main_mgr, main_task_thread = MgrFactory().get_objs()
  main_task_thread.start()
  logger.info('main_mgr task thread started.')


def my_init():
  signal.signal(signal.SIGINT, signal_handler)
  signal.signal(signal.SIGTERM, signal_handler)
  init_main_mgr_task()

  # 如果数据库文件不存在，则创建它
  database_path = os.path.join(app.instance_path, app.config['DATABASE_FILE'])
  if not os.path.exists(database_path):
    with app.app_context():
      build_init_db()


################# End of 管理 相关 #################

if __name__ == '__main__':
  my_init()
  app.run(debug=True, port=17028,
          use_reloader=False)  # D:\tools\anaconda3\envs\docu_dl_analyze\python.exe: can't open file 'C:\\Users\\dzy\\AppData\\Local\\Programs\\PyCharm': [Errno 2] No such file or directory

r"""
  以 flask-admin-master.zip 的 example\auth-flask-login 为基础 ，
"""
